package com.google.code.p.restrpc.client.proxy;

import java.net.URI;
import java.net.URISyntaxException;

import com.google.code.p.restrpc.DefaultRpcResponse;
import com.google.code.p.restrpc.RestInvoker;
import com.google.code.p.restrpc.RpcClientException;
import com.google.code.p.restrpc.RpcException;
import com.google.code.p.restrpc.RpcRequest;
import com.google.code.p.restrpc.RpcResponse;
import com.google.code.p.restrpc.UnsupportedTransferFormatException;
import com.google.code.p.restrpc.client.serial.FormatSerializationProvider;
import com.google.code.p.restrpc.client.serial.FormatSerializationProviderManager;
import com.google.code.p.restrpc.http.HttpClientNotFoundException;
import com.google.code.p.restrpc.http.HttpClientRefusedException;
import com.google.code.p.restrpc.http.HttpConnectException;
import com.google.code.p.restrpc.http.RestTemplate;

/**
 * @author dz
 */
public class DefaultRestInvoker implements RestInvoker {
    
    private RestTemplate restTemplate;
    
    /**
     * @param restTemplate
     */
    public DefaultRestInvoker(RestTemplate restTemplate) {
        if (restTemplate == null) {
            throw new IllegalArgumentException("The 'restTemplate' must be not null!");
        }
        
        this.restTemplate = restTemplate;
    }
    
    /**
     * 
     */
    public RpcResponse send(final RpcRequest rpcRequest) throws RpcException {
        URI targetUrl = null;
        try {
            targetUrl = getInvokeUrl(rpcRequest);
        }
        catch (URISyntaxException e) {
            throw new RpcException("Invalid url", e);
        }
        
        FormatSerializationProvider formatSerializationProvider = FormatSerializationProviderManager.getFormatSerializationProvider(rpcRequest.getTransferFormat());
        if (formatSerializationProvider == null) {
            throw new UnsupportedTransferFormatException(rpcRequest.getTransferFormat()
                                                                   .value());
        }
        
        try {
            return (DefaultRpcResponse) restTemplate.post(targetUrl,
                                                          formatSerializationProvider.getHttpRequestCallback(rpcRequest),
                                                          formatSerializationProvider.getHttpResponseCallback(),
                                                          formatSerializationProvider.getErrorHandler());
        }
        catch (HttpClientNotFoundException e) {
            throw new RemoteServiceUnavailableException(rpcRequest.getMethodName(),
                                                        e);
        }
        catch (HttpClientRefusedException e) {
            throw new RemoteServiceRefusedException(rpcRequest.getMethodName(),
                                                    e);
        }
        catch (HttpConnectException e) {
            throw new RemoteNodeRefusedException(rpcRequest.getAddress() + ":"
                                                 + rpcRequest.getPort(), e);
        }
        catch (Throwable e) {
            throw new RpcClientException(e);
        }
    }
    
    protected URI getInvokeUrl(RpcRequest request) throws URISyntaxException {
        StringBuilder sb = new StringBuilder();
        sb.append(request.getProtocal().prefix()).append(request.getAddress());
        if (request.getPort() != 80) {
            sb.append(":").append(request.getPort());
        }
        sb.append(request.getContext());
        if (!request.getContext().endsWith("/") && !request.getServiceName()
                                                           .startsWith("/")) {
            sb.append("/");
        }
        String urlString = sb.append(request.getServiceName())
                             .append("/")
                             .append(request.getMethodName())
                             .toString();
        
        return new URI(urlString);
    }
    
}
